/*-------------------------------------------------------------------------
 *
 * tsquery_gist.c
 *	  GiST 索引支持 tsquery
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/tsquery_gist.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/gist.h"
#include "access/stratnum.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"

#define GETENTRY(vec,pos) DatumGetTSQuerySign((vec)->vector[pos].key)


Datum gtsquery_compress(PG_FUNCTION_ARGS)
{
	GISTENTRY  *fc_entry = (GISTENTRY *) PG_GETARG_POINTER(0);
	GISTENTRY  *fc_retval = fc_entry;

	if (fc_entry->leafkey)
	{
		TSQuerySign fc_sign;

		fc_retval = (GISTENTRY *) palloc(sizeof(GISTENTRY));
		fc_sign = makeTSQuerySign(DatumGetTSQuery(fc_entry->key));

		gistentryinit(*fc_retval, TSQuerySignGetDatum(fc_sign),
					  fc_entry->rel, fc_entry->page,
					  fc_entry->offset, false);
	}

	PG_RETURN_POINTER(fc_retval);
}

/* 我们不需要解压缩函数，因为其他 gtsquery 支持函数使用压缩表示。 */

Datum gtsquery_consistent(PG_FUNCTION_ARGS)
{
	GISTENTRY  *fc_entry = (GISTENTRY *) PG_GETARG_POINTER(0);
	TSQuery		fc_query = PG_GETARG_TSQUERY(1);
	StrategyNumber fc_strategy = (StrategyNumber) PG_GETARG_UINT16(2);

	/* Oid		subtype = PG_GETARG_OID(3); */
	bool	   *fc_recheck = (bool *) PG_GETARG_POINTER(4);
	TSQuerySign fc_key = DatumGetTSQuerySign(fc_entry->key);
	TSQuerySign fc_sq = makeTSQuerySign(fc_query);
	bool		fc_retval;

	/* 此函数处理的所有情况都是不准确的 */
	*fc_recheck = true;

	switch (fc_strategy)
	{
		case RTContainsStrategyNumber:
			if (GIST_LEAF(fc_entry))
				fc_retval = (fc_key & fc_sq) == fc_sq;
			else
				fc_retval = (fc_key & fc_sq) != 0;
			break;
		case RTContainedByStrategyNumber:
			if (GIST_LEAF(fc_entry))
				fc_retval = (fc_key & fc_sq) == fc_key;
			else
				fc_retval = (fc_key & fc_sq) != 0;
			break;
		default:
			fc_retval = false;
	}
	PG_RETURN_BOOL(fc_retval);
}

Datum gtsquery_union(PG_FUNCTION_ARGS)
{
	GistEntryVector *fc_entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
	int		   *fc_size = (int *) PG_GETARG_POINTER(1);
	TSQuerySign fc_sign;
	int			fc_i;

	fc_sign = 0;

	for (fc_i = 0; fc_i < fc_entryvec->n; fc_i++)
		fc_sign |= GETENTRY(fc_entryvec, fc_i);

	*fc_size = sizeof(TSQuerySign);

	PG_RETURN_TSQUERYSIGN(fc_sign);
}

Datum gtsquery_same(PG_FUNCTION_ARGS)
{
	TSQuerySign fc_a = PG_GETARG_TSQUERYSIGN(0);
	TSQuerySign fc_b = PG_GETARG_TSQUERYSIGN(1);
	bool	   *fc_result = (bool *) PG_GETARG_POINTER(2);

	*fc_result = (fc_a == fc_b);

	PG_RETURN_POINTER(fc_result);
}

static int fc_sizebitvec(TSQuerySign fc_sign)
{
	int			fc_size = 0,
				fc_i;

	for (fc_i = 0; fc_i < TSQS_SIGLEN; fc_i++)
		fc_size += 0x01 & (fc_sign >> fc_i);

	return fc_size;
}

static int fc_hemdist(TSQuerySign fc_a, TSQuerySign fc_b)
{
	TSQuerySign fc_res = fc_a ^ fc_b;

	return fc_sizebitvec(fc_res);
}

Datum gtsquery_penalty(PG_FUNCTION_ARGS)
{
	TSQuerySign fc_origval = DatumGetTSQuerySign(((GISTENTRY *) PG_GETARG_POINTER(0))->key);
	TSQuerySign fc_newval = DatumGetTSQuerySign(((GISTENTRY *) PG_GETARG_POINTER(1))->key);
	float	   *fc_penalty = (float *) PG_GETARG_POINTER(2);

	*fc_penalty = fc_hemdist(fc_origval, fc_newval);

	PG_RETURN_POINTER(fc_penalty);
}


typedef struct
{
	OffsetNumber pos;
	int32		cost;
} SPLITCOST;

static int fc_comparecost(const void *fc_a, const void *fc_b)
{
	if (((const SPLITCOST *) fc_a)->cost == ((const SPLITCOST *) fc_b)->cost)
		return 0;
	else
		return (((const SPLITCOST *) fc_a)->cost > ((const SPLITCOST *) fc_b)->cost) ? 1 : -1;
}

#define WISH_F(a,b,c) (double)( -(double)(((a)-(b))*((a)-(b))*((a)-(b)))*(c) )

Datum gtsquery_picksplit(PG_FUNCTION_ARGS)
{
	GistEntryVector *fc_entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
	GIST_SPLITVEC *fc_v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
	OffsetNumber fc_maxoff = fc_entryvec->n - 2;
	OffsetNumber fc_k,
				fc_j;
	TSQuerySign fc_datum_l,
				fc_datum_r;
	int32		fc_size_alpha,
				fc_size_beta;
	int32		fc_size_waste,
				fc_waste = -1;
	int32		fc_nbytes;
	OffsetNumber fc_seed_1 = 0,
				fc_seed_2 = 0;
	OffsetNumber *fc_left,
			   *fc_right;

	SPLITCOST  *fc_costvector;

	fc_nbytes = (fc_maxoff + 2) * sizeof(OffsetNumber);
	fc_left = fc_v->spl_left = (OffsetNumber *) palloc(fc_nbytes);
	fc_right = fc_v->spl_right = (OffsetNumber *) palloc(fc_nbytes);
	fc_v->spl_nleft = fc_v->spl_nright = 0;

	for (fc_k = FirstOffsetNumber; fc_k < fc_maxoff; fc_k = OffsetNumberNext(fc_k))
		for (fc_j = OffsetNumberNext(fc_k); fc_j <= fc_maxoff; fc_j = OffsetNumberNext(fc_j))
		{
			fc_size_waste = fc_hemdist(GETENTRY(fc_entryvec, fc_j), GETENTRY(fc_entryvec, fc_k));
			if (fc_size_waste > fc_waste)
			{
				fc_waste = fc_size_waste;
				fc_seed_1 = fc_k;
				fc_seed_2 = fc_j;
			}
		}


	if (fc_seed_1 == 0 || fc_seed_2 == 0)
	{
		fc_seed_1 = 1;
		fc_seed_2 = 2;
	}

	fc_datum_l = GETENTRY(fc_entryvec, fc_seed_1);
	fc_datum_r = GETENTRY(fc_entryvec, fc_seed_2);

	fc_maxoff = OffsetNumberNext(fc_maxoff);
	fc_costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * fc_maxoff);
	for (fc_j = FirstOffsetNumber; fc_j <= fc_maxoff; fc_j = OffsetNumberNext(fc_j))
	{
		fc_costvector[fc_j - 1].pos = fc_j;
		fc_size_alpha = fc_hemdist(GETENTRY(fc_entryvec, fc_seed_1), GETENTRY(fc_entryvec, fc_j));
		fc_size_beta = fc_hemdist(GETENTRY(fc_entryvec, fc_seed_2), GETENTRY(fc_entryvec, fc_j));
		fc_costvector[fc_j - 1].cost = abs(fc_size_alpha - fc_size_beta);
	}
	qsort((void *) fc_costvector, fc_maxoff, sizeof(SPLITCOST), fc_comparecost);

	for (fc_k = 0; fc_k < fc_maxoff; fc_k++)
	{
		fc_j = fc_costvector[fc_k].pos;
		if (fc_j == fc_seed_1)
		{
			*fc_left++ = fc_j;
			fc_v->spl_nleft++;
			continue;
		}
		else if (fc_j == fc_seed_2)
		{
			*fc_right++ = fc_j;
			fc_v->spl_nright++;
			continue;
		}
		fc_size_alpha = fc_hemdist(fc_datum_l, GETENTRY(fc_entryvec, fc_j));
		fc_size_beta = fc_hemdist(fc_datum_r, GETENTRY(fc_entryvec, fc_j));

		if (fc_size_alpha < fc_size_beta + WISH_F(fc_v->spl_nleft, fc_v->spl_nright, 0.05))
		{
			fc_datum_l |= GETENTRY(fc_entryvec, fc_j);
			*fc_left++ = fc_j;
			fc_v->spl_nleft++;
		}
		else
		{
			fc_datum_r |= GETENTRY(fc_entryvec, fc_j);
			*fc_right++ = fc_j;
			fc_v->spl_nright++;
		}
	}

	*fc_right = *fc_left = FirstOffsetNumber;
	fc_v->spl_ldatum = TSQuerySignGetDatum(fc_datum_l);
	fc_v->spl_rdatum = TSQuerySignGetDatum(fc_datum_r);

	PG_RETURN_POINTER(fc_v);
}

/* 以前，gtsquery_consistent 在 pg_proc.h 中声明，参数与 GiST 支持函数的文档规定的约定不匹配。我们已修复此问题，但仍需要一个具有旧签名的 pg_proc 条目，以支持重新加载预 9.6 contrib/tsearch2 操作类声明。这个兼容性函数最终应该消失。 */
Datum gtsquery_consistent_oldsig(PG_FUNCTION_ARGS)
{
	return gtsquery_consistent(fcinfo);
}
